baubs git
Commit 7cf4a5de17ecc4fb48265fec4e57ddb6100651f7
Parents : bb066d7
Author : Jan-Henrik Bruhn <jan-henrik.bruhn@offis.de>
Date : 2025-12-20T20:12:40+01:00
feature: Migrate PatternCanvas to shadcn/ui and rename placeholder
- Migrated PatternCanvas component to use shadcn Card components (Card, CardHeader, CardTitle, CardDescription, CardContent)
- Replaced custom zoom control buttons with shadcn Button component using outline variant and icon size
- Renamed PatternPreviewPlaceholder to PatternCanvasPlaceholder for consistency
- Updated all imports and references in App.tsx
- Maintained all existing functionality including Konva canvas rendering, zoom controls, and pattern positioning
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changes
4 files changed, 336 insertions(+), 316 deletions(-)
Diff
diff --git a/src/App.tsx b/src/App.tsx
index 4c94540..a42724f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -6,7 +6,7 @@ import { useUIStore } from "./stores/useUIStore";
import { AppHeader } from "./components/AppHeader";
import { LeftSidebar } from "./components/LeftSidebar";
import { PatternCanvas } from "./components/PatternCanvas";
-import { PatternPreviewPlaceholder } from "./components/PatternPreviewPlaceholder";
+import { PatternCanvasPlaceholder } from "./components/PatternCanvasPlaceholder";
import { BluetoothDevicePicker } from "./components/BluetoothDevicePicker";
import "./App.css";
@@ -76,7 +76,7 @@ function App() {
{/* Right Column - Pattern Preview */}
<div className="flex flex-col lg:overflow-hidden lg:h-full">
- {pesData ? <PatternCanvas /> : <PatternPreviewPlaceholder />}
+ {pesData ? <PatternCanvas /> : <PatternCanvasPlaceholder />}
</div>
</div>
diff --git a/src/components/PatternCanvas.tsx b/src/components/PatternCanvas.tsx
index b9099b8..249784d 100644
--- a/src/components/PatternCanvas.tsx
+++ b/src/components/PatternCanvas.tsx
@@ -22,6 +22,14 @@ import {
PatternBounds,
CurrentPosition,
} from "./KonvaComponents";
+import {
+ Card,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+ CardContent,
+} from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
export function PatternCanvas() {
// Machine store
@@ -252,126 +260,101 @@ export function PatternCanvas() {
: "text-gray-600 dark:text-gray-400";
return (
- <div
- className={`lg:h-full bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 ${borderColor} flex flex-col`}
+ <Card
+ className={`p-0 gap-0 lg:h-full flex flex-col border-l-4 ${borderColor}`}
>
- <div className="flex items-start gap-3 mb-3 flex-shrink-0">
- <PhotoIcon className={`w-6 h-6 ${iconColor} flex-shrink-0 mt-0.5`} />
- <div className="flex-1 min-w-0">
- <h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">
- Pattern Preview
- </h3>
- {pesData ? (
- <p className="text-xs text-gray-600 dark:text-gray-400">
- {((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)} ×{" "}
- {((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)} mm
- </p>
- ) : (
- <p className="text-xs text-gray-600 dark:text-gray-400">
- No pattern loaded
- </p>
- )}
+ <CardHeader className="p-4 pb-3">
+ <div className="flex items-start gap-3">
+ <PhotoIcon className={`w-6 h-6 ${iconColor} flex-shrink-0 mt-0.5`} />
+ <div className="flex-1 min-w-0">
+ <CardTitle className="text-sm">Pattern Preview</CardTitle>
+ {pesData ? (
+ <CardDescription className="text-xs">
+ {((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)}{" "}
+ ×{" "}
+ {((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)}{" "}
+ mm
+ </CardDescription>
+ ) : (
+ <CardDescription className="text-xs">
+ No pattern loaded
+ </CardDescription>
+ )}
+ </div>
</div>
- </div>
- <div
- className="relative w-full h-[400px] sm:h-[500px] lg:flex-1 lg:min-h-0 border border-gray-300 dark:border-gray-600 rounded bg-gray-200 dark:bg-gray-900 overflow-hidden"
- ref={containerRef}
- >
- {containerSize.width > 0 && (
- <Stage
- width={containerSize.width}
- height={containerSize.height}
- x={stagePos.x}
- y={stagePos.y}
- scaleX={stageScale}
- scaleY={stageScale}
- draggable
- onWheel={handleWheel}
- onDragStart={() => {
- if (stageRef.current) {
- stageRef.current.container().style.cursor = "grabbing";
- }
- }}
- onDragEnd={() => {
- if (stageRef.current) {
- stageRef.current.container().style.cursor = "grab";
- }
- }}
- ref={(node) => {
- stageRef.current = node;
- if (node) {
- node.container().style.cursor = "grab";
- }
- }}
- >
- {/* Background layer: grid, origin, hoop */}
- <Layer>
- {pesData && (
- <>
- <Grid
- gridSize={100}
- bounds={pesData.bounds}
- machineInfo={machineInfo}
- />
- <Origin />
- {machineInfo && <Hoop machineInfo={machineInfo} />}
- </>
- )}
- </Layer>
-
- {/* Pattern layer: draggable stitches and bounds */}
- <Layer>
- {pesData && (
- <Group
- name="pattern-group"
- draggable={!patternUploaded && !isUploading}
- x={localPatternOffset.x}
- y={localPatternOffset.y}
- onDragEnd={handlePatternDragEnd}
- onMouseEnter={(e) => {
- const stage = e.target.getStage();
- if (stage && !patternUploaded && !isUploading)
- stage.container().style.cursor = "move";
- }}
- onMouseLeave={(e) => {
- const stage = e.target.getStage();
- if (stage && !patternUploaded && !isUploading)
- stage.container().style.cursor = "grab";
- }}
- >
- <Stitches
- stitches={pesData.penStitches.stitches.map(
- (s, i): [number, number, number, number] => {
- // Convert PEN stitch format {x, y, flags, isJump} to PES format [x, y, cmd, colorIndex]
- const cmd = s.isJump ? 0x10 : 0; // MOVE flag if jump
- const colorIndex =
- pesData.penStitches.colorBlocks.find(
- (b) => i >= b.startStitch && i <= b.endStitch,
- )?.colorIndex ?? 0;
- return [s.x, s.y, cmd, colorIndex];
- },
- )}
- pesData={pesData}
- currentStitchIndex={sewingProgress?.currentStitch || 0}
- showProgress={patternUploaded || isUploading}
- />
- <PatternBounds bounds={pesData.bounds} />
- </Group>
- )}
- </Layer>
-
- {/* Current position layer */}
- <Layer>
- {pesData &&
- pesData.penStitches &&
- sewingProgress &&
- sewingProgress.currentStitch > 0 && (
- <Group x={localPatternOffset.x} y={localPatternOffset.y}>
- <CurrentPosition
- currentStitchIndex={sewingProgress.currentStitch}
+ </CardHeader>
+ <CardContent className="px-4 pt-0 pb-4 flex-1 flex flex-col">
+ <div
+ className="relative w-full h-[400px] sm:h-[500px] lg:flex-1 lg:min-h-0 border border-gray-300 dark:border-gray-600 rounded bg-gray-200 dark:bg-gray-900 overflow-hidden"
+ ref={containerRef}
+ >
+ {containerSize.width > 0 && (
+ <Stage
+ width={containerSize.width}
+ height={containerSize.height}
+ x={stagePos.x}
+ y={stagePos.y}
+ scaleX={stageScale}
+ scaleY={stageScale}
+ draggable
+ onWheel={handleWheel}
+ onDragStart={() => {
+ if (stageRef.current) {
+ stageRef.current.container().style.cursor = "grabbing";
+ }
+ }}
+ onDragEnd={() => {
+ if (stageRef.current) {
+ stageRef.current.container().style.cursor = "grab";
+ }
+ }}
+ ref={(node) => {
+ stageRef.current = node;
+ if (node) {
+ node.container().style.cursor = "grab";
+ }
+ }}
+ >
+ {/* Background layer: grid, origin, hoop */}
+ <Layer>
+ {pesData && (
+ <>
+ <Grid
+ gridSize={100}
+ bounds={pesData.bounds}
+ machineInfo={machineInfo}
+ />
+ <Origin />
+ {machineInfo && <Hoop machineInfo={machineInfo} />}
+ </>
+ )}
+ </Layer>
+
+ {/* Pattern layer: draggable stitches and bounds */}
+ <Layer>
+ {pesData && (
+ <Group
+ name="pattern-group"
+ draggable={!patternUploaded && !isUploading}
+ x={localPatternOffset.x}
+ y={localPatternOffset.y}
+ onDragEnd={handlePatternDragEnd}
+ onMouseEnter={(e) => {
+ const stage = e.target.getStage();
+ if (stage && !patternUploaded && !isUploading)
+ stage.container().style.cursor = "move";
+ }}
+ onMouseLeave={(e) => {
+ const stage = e.target.getStage();
+ if (stage && !patternUploaded && !isUploading)
+ stage.container().style.cursor = "grab";
+ }}
+ >
+ <Stitches
stitches={pesData.penStitches.stitches.map(
(s, i): [number, number, number, number] => {
- const cmd = s.isJump ? 0x10 : 0;
+ // Convert PEN stitch format {x, y, flags, isJump} to PES format [x, y, cmd, colorIndex]
+ const cmd = s.isJump ? 0x10 : 0; // MOVE flag if jump
const colorIndex =
pesData.penStitches.colorBlocks.find(
(b) => i >= b.startStitch && i <= b.endStitch,
@@ -379,138 +362,175 @@ export function PatternCanvas() {
return [s.x, s.y, cmd, colorIndex];
},
)}
+ pesData={pesData}
+ currentStitchIndex={sewingProgress?.currentStitch || 0}
+ showProgress={patternUploaded || isUploading}
/>
+ <PatternBounds bounds={pesData.bounds} />
</Group>
)}
- </Layer>
- </Stage>
- )}
-
- {/* Placeholder overlay when no pattern is loaded */}
- {!pesData && (
- <div className="flex items-center justify-center h-full text-gray-600 dark:text-gray-400 italic">
- Load a PES file to preview the pattern
- </div>
- )}
-
- {/* Pattern info overlays */}
- {pesData && (
- <>
- {/* Thread Legend Overlay */}
- <div className="absolute top-2 sm:top-2.5 left-2 sm:left-2.5 bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm p-2 sm:p-2.5 rounded-lg shadow-lg z-10 max-w-[150px] sm:max-w-[180px] lg:max-w-[200px]">
- <h4 className="m-0 mb-1.5 sm:mb-2 text-xs font-semibold text-gray-900 dark:text-gray-100 border-b border-gray-300 dark:border-gray-600 pb-1 sm:pb-1.5">
- Colors
- </h4>
- {pesData.uniqueColors.map((color, idx) => {
- // Primary metadata: brand and catalog number
- const primaryMetadata = [
- color.brand,
- color.catalogNumber ? `#${color.catalogNumber}` : null,
- ]
- .filter(Boolean)
- .join(" ");
-
- // Secondary metadata: chart and description
- const secondaryMetadata = [color.chart, color.description]
- .filter(Boolean)
- .join(" ");
-
- return (
- <div
- key={idx}
- className="flex items-start gap-1.5 sm:gap-2 mb-1 sm:mb-1.5 last:mb-0"
- >
+ </Layer>
+
+ {/* Current position layer */}
+ <Layer>
+ {pesData &&
+ pesData.penStitches &&
+ sewingProgress &&
+ sewingProgress.currentStitch > 0 && (
+ <Group x={localPatternOffset.x} y={localPatternOffset.y}>
+ <CurrentPosition
+ currentStitchIndex={sewingProgress.currentStitch}
+ stitches={pesData.penStitches.stitches.map(
+ (s, i): [number, number, number, number] => {
+ const cmd = s.isJump ? 0x10 : 0;
+ const colorIndex =
+ pesData.penStitches.colorBlocks.find(
+ (b) => i >= b.startStitch && i <= b.endStitch,
+ )?.colorIndex ?? 0;
+ return [s.x, s.y, cmd, colorIndex];
+ },
+ )}
+ />
+ </Group>
+ )}
+ </Layer>
+ </Stage>
+ )}
+
+ {/* Placeholder overlay when no pattern is loaded */}
+ {!pesData && (
+ <div className="flex items-center justify-center h-full text-gray-600 dark:text-gray-400 italic">
+ Load a PES file to preview the pattern
+ </div>
+ )}
+
+ {/* Pattern info overlays */}
+ {pesData && (
+ <>
+ {/* Thread Legend Overlay */}
+ <div className="absolute top-2 sm:top-2.5 left-2 sm:left-2.5 bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm p-2 sm:p-2.5 rounded-lg shadow-lg z-10 max-w-[150px] sm:max-w-[180px] lg:max-w-[200px]">
+ <h4 className="m-0 mb-1.5 sm:mb-2 text-xs font-semibold text-gray-900 dark:text-gray-100 border-b border-gray-300 dark:border-gray-600 pb-1 sm:pb-1.5">
+ Colors
+ </h4>
+ {pesData.uniqueColors.map((color, idx) => {
+ // Primary metadata: brand and catalog number
+ const primaryMetadata = [
+ color.brand,
+ color.catalogNumber ? `#${color.catalogNumber}` : null,
+ ]
+ .filter(Boolean)
+ .join(" ");
+
+ // Secondary metadata: chart and description
+ const secondaryMetadata = [color.chart, color.description]
+ .filter(Boolean)
+ .join(" ");
+
+ return (
<div
- className="w-3 h-3 sm:w-4 sm:h-4 rounded border border-black dark:border-gray-300 flex-shrink-0 mt-0.5"
- style={{ backgroundColor: color.hex }}
- />
- <div className="flex-1 min-w-0">
- <div className="text-xs font-semibold text-gray-900 dark:text-gray-100">
- Color {idx + 1}
- </div>
- {(primaryMetadata || secondaryMetadata) && (
- <div className="text-xs text-gray-600 dark:text-gray-400 leading-tight mt-0.5 break-words">
- {primaryMetadata}
- {primaryMetadata && secondaryMetadata && (
- <span className="mx-1">•</span>
- )}
- {secondaryMetadata}
+ key={idx}
+ className="flex items-start gap-1.5 sm:gap-2 mb-1 sm:mb-1.5 last:mb-0"
+ >
+ <div
+ className="w-3 h-3 sm:w-4 sm:h-4 rounded border border-black dark:border-gray-300 flex-shrink-0 mt-0.5"
+ style={{ backgroundColor: color.hex }}
+ />
+ <div className="flex-1 min-w-0">
+ <div className="text-xs font-semibold text-gray-900 dark:text-gray-100">
+ Color {idx + 1}
</div>
- )}
+ {(primaryMetadata || secondaryMetadata) && (
+ <div className="text-xs text-gray-600 dark:text-gray-400 leading-tight mt-0.5 break-words">
+ {primaryMetadata}
+ {primaryMetadata && secondaryMetadata && (
+ <span className="mx-1">•</span>
+ )}
+ {secondaryMetadata}
+ </div>
+ )}
+ </div>
</div>
- </div>
- );
- })}
- </div>
+ );
+ })}
+ </div>
- {/* Pattern Offset Indicator */}
- <div
- className={`absolute bottom-16 sm:bottom-20 right-2 sm:right-5 backdrop-blur-sm p-2 sm:p-2.5 px-2.5 sm:px-3.5 rounded-lg shadow-lg z-[11] min-w-[160px] sm:min-w-[180px] transition-colors ${
- patternUploaded
- ? "bg-amber-50/95 dark:bg-amber-900/80 border-2 border-amber-300 dark:border-amber-600"
- : "bg-white/95 dark:bg-gray-800/95"
- }`}
- >
- <div className="flex items-center justify-between mb-1">
- <div className="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
- Pattern Position:
- </div>
- {patternUploaded && (
- <div className="flex items-center gap-1 text-amber-600 dark:text-amber-400">
- <LockClosedIcon className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
- <span className="text-xs font-bold">LOCKED</span>
+ {/* Pattern Offset Indicator */}
+ <div
+ className={`absolute bottom-16 sm:bottom-20 right-2 sm:right-5 backdrop-blur-sm p-2 sm:p-2.5 px-2.5 sm:px-3.5 rounded-lg shadow-lg z-[11] min-w-[160px] sm:min-w-[180px] transition-colors ${
+ patternUploaded
+ ? "bg-amber-50/95 dark:bg-amber-900/80 border-2 border-amber-300 dark:border-amber-600"
+ : "bg-white/95 dark:bg-gray-800/95"
+ }`}
+ >
+ <div className="flex items-center justify-between mb-1">
+ <div className="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
+ Pattern Position:
</div>
- )}
- </div>
- <div className="text-sm font-semibold text-primary-600 dark:text-primary-400 mb-1">
- X: {(localPatternOffset.x / 10).toFixed(1)}mm, Y:{" "}
- {(localPatternOffset.y / 10).toFixed(1)}mm
- </div>
- <div className="text-xs text-gray-600 dark:text-gray-400 italic">
- {patternUploaded
- ? "Pattern locked • Drag background to pan"
- : "Drag pattern to move • Drag background to pan"}
+ {patternUploaded && (
+ <div className="flex items-center gap-1 text-amber-600 dark:text-amber-400">
+ <LockClosedIcon className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
+ <span className="text-xs font-bold">LOCKED</span>
+ </div>
+ )}
+ </div>
+ <div className="text-sm font-semibold text-primary-600 dark:text-primary-400 mb-1">
+ X: {(localPatternOffset.x / 10).toFixed(1)}mm, Y:{" "}
+ {(localPatternOffset.y / 10).toFixed(1)}mm
+ </div>
+ <div className="text-xs text-gray-600 dark:text-gray-400 italic">
+ {patternUploaded
+ ? "Pattern locked • Drag background to pan"
+ : "Drag pattern to move • Drag background to pan"}
+ </div>
</div>
- </div>
- {/* Zoom Controls Overlay */}
- <div className="absolute bottom-2 sm:bottom-5 right-2 sm:right-5 flex gap-1.5 sm:gap-2 items-center bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm px-2 sm:px-3 py-1.5 sm:py-2 rounded-lg shadow-lg z-10">
- <button
- className="w-7 h-7 sm:w-8 sm:h-8 p-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded cursor-pointer transition-all flex items-center justify-center hover:bg-primary-600 hover:text-white hover:border-primary-600 dark:hover:border-primary-600 hover:shadow-md hover:shadow-primary-600/30 disabled:opacity-50 disabled:cursor-not-allowed"
- onClick={handleCenterPattern}
- disabled={!pesData || patternUploaded || isUploading}
- title="Center Pattern in Hoop"
- >
- <ArrowsPointingInIcon className="w-4 h-4 sm:w-5 sm:h-5 dark:text-gray-200" />
- </button>
- <button
- className="w-7 h-7 sm:w-8 sm:h-8 p-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded cursor-pointer transition-all flex items-center justify-center hover:bg-primary-600 hover:text-white hover:border-primary-600 dark:hover:border-primary-600 hover:shadow-md hover:shadow-primary-600/30 disabled:opacity-50 disabled:cursor-not-allowed"
- onClick={handleZoomIn}
- title="Zoom In"
- >
- <PlusIcon className="w-4 h-4 sm:w-5 sm:h-5 dark:text-gray-200" />
- </button>
- <span className="min-w-[40px] sm:min-w-[50px] text-center text-sm font-semibold text-gray-900 dark:text-gray-100 select-none">
- {Math.round(stageScale * 100)}%
- </span>
- <button
- className="w-7 h-7 sm:w-8 sm:h-8 p-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded cursor-pointer transition-all flex items-center justify-center hover:bg-primary-600 hover:text-white hover:border-primary-600 dark:hover:border-primary-600 hover:shadow-md hover:shadow-primary-600/30 disabled:opacity-50 disabled:cursor-not-allowed"
- onClick={handleZoomOut}
- title="Zoom Out"
- >
- <MinusIcon className="w-4 h-4 sm:w-5 sm:h-5 dark:text-gray-200" />
- </button>
- <button
- className="w-7 h-7 sm:w-8 sm:h-8 p-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded cursor-pointer transition-all flex items-center justify-center hover:bg-primary-600 hover:text-white hover:border-primary-600 dark:hover:border-primary-600 hover:shadow-md hover:shadow-primary-600/30 disabled:opacity-50 disabled:cursor-not-allowed ml-1"
- onClick={handleZoomReset}
- title="Reset Zoom"
- >
- <ArrowPathIcon className="w-4 h-4 sm:w-5 sm:h-5 dark:text-gray-200" />
- </button>
- </div>
- </>
- )}
- </div>
- </div>
+ {/* Zoom Controls Overlay */}
+ <div className="absolute bottom-2 sm:bottom-5 right-2 sm:right-5 flex gap-1.5 sm:gap-2 items-center bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm px-2 sm:px-3 py-1.5 sm:py-2 rounded-lg shadow-lg z-10">
+ <Button
+ variant="outline"
+ size="icon"
+ className="w-7 h-7 sm:w-8 sm:h-8"
+ onClick={handleCenterPattern}
+ disabled={!pesData || patternUploaded || isUploading}
+ title="Center Pattern in Hoop"
+ >
+ <ArrowsPointingInIcon className="w-4 h-4 sm:w-5 sm:h-5" />
+ </Button>
+ <Button
+ variant="outline"
+ size="icon"
+ className="w-7 h-7 sm:w-8 sm:h-8"
+ onClick={handleZoomIn}
+ title="Zoom In"
+ >
+ <PlusIcon className="w-4 h-4 sm:w-5 sm:h-5" />
+ </Button>
+ <span className="min-w-[40px] sm:min-w-[50px] text-center text-sm font-semibold text-gray-900 dark:text-gray-100 select-none">
+ {Math.round(stageScale * 100)}%
+ </span>
+ <Button
+ variant="outline"
+ size="icon"
+ className="w-7 h-7 sm:w-8 sm:h-8"
+ onClick={handleZoomOut}
+ title="Zoom Out"
+ >
+ <MinusIcon className="w-4 h-4 sm:w-5 sm:h-5" />
+ </Button>
+ <Button
+ variant="outline"
+ size="icon"
+ className="w-7 h-7 sm:w-8 sm:h-8 ml-1"
+ onClick={handleZoomReset}
+ title="Reset Zoom"
+ >
+ <ArrowPathIcon className="w-4 h-4 sm:w-5 sm:h-5" />
+ </Button>
+ </div>
+ </>
+ )}
+ </div>
+ </CardContent>
+ </Card>
);
}
diff --git a/src/components/PatternCanvasPlaceholder.tsx b/src/components/PatternCanvasPlaceholder.tsx
new file mode 100644
index 0000000..d2ebf1e
--- /dev/null
+++ b/src/components/PatternCanvasPlaceholder.tsx
@@ -0,0 +1,75 @@
+import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
+
+export function PatternCanvasPlaceholder() {
+ return (
+ <Card className="p-0 gap-0 lg:h-full animate-fadeIn flex flex-col">
+ <CardHeader className="p-6 pb-4 border-b-2 border-gray-300 dark:border-gray-600">
+ <CardTitle className="text-base lg:text-lg">Pattern Preview</CardTitle>
+ </CardHeader>
+ <CardContent className="p-6 flex-1 flex flex-col">
+ <div className="h-[400px] sm:h-[500px] lg:flex-1 flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-700 dark:to-gray-800 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600 relative overflow-hidden">
+ {/* Decorative background pattern */}
+ <div className="absolute inset-0 opacity-5 dark:opacity-10">
+ <div className="absolute top-10 left-10 w-32 h-32 border-4 border-gray-400 dark:border-gray-500 rounded-full"></div>
+ <div className="absolute bottom-10 right-10 w-40 h-40 border-4 border-gray-400 dark:border-gray-500 rounded-full"></div>
+ <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-48 h-48 border-4 border-gray-400 dark:border-gray-500 rounded-full"></div>
+ </div>
+
+ <div className="text-center relative z-10">
+ <div className="relative inline-block mb-6">
+ <svg
+ className="w-28 h-28 mx-auto text-gray-300 dark:text-gray-600"
+ fill="none"
+ stroke="currentColor"
+ viewBox="0 0 24 24"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth={1.5}
+ d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"
+ />
+ </svg>
+ <div className="absolute -top-2 -right-2 w-8 h-8 bg-primary-100 dark:bg-primary-900 rounded-full flex items-center justify-center">
+ <svg
+ className="w-5 h-5 text-primary-600 dark:text-primary-400"
+ fill="none"
+ stroke="currentColor"
+ viewBox="0 0 24 24"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth={2}
+ d="M12 4v16m8-8H4"
+ />
+ </svg>
+ </div>
+ </div>
+ <h3 className="text-gray-700 dark:text-gray-200 text-base lg:text-lg font-semibold mb-2">
+ No Pattern Loaded
+ </h3>
+ <p className="text-gray-500 dark:text-gray-400 text-sm mb-4 max-w-sm mx-auto">
+ Connect to your machine and choose a PES embroidery file to see
+ your design preview
+ </p>
+ <div className="flex items-center justify-center gap-6 text-xs text-gray-400 dark:text-gray-500">
+ <div className="flex items-center gap-1">
+ <div className="w-2 h-2 bg-primary-400 dark:bg-primary-500 rounded-full"></div>
+ <span>Drag to Position</span>
+ </div>
+ <div className="flex items-center gap-1">
+ <div className="w-2 h-2 bg-success-400 dark:bg-success-500 rounded-full"></div>
+ <span>Zoom & Pan</span>
+ </div>
+ <div className="flex items-center gap-1">
+ <div className="w-2 h-2 bg-accent-400 dark:bg-accent-500 rounded-full"></div>
+ <span>Real-time Preview</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+ );
+}
diff --git a/src/components/PatternPreviewPlaceholder.tsx b/src/components/PatternPreviewPlaceholder.tsx
deleted file mode 100644
index 6e4cb01..0000000
--- a/src/components/PatternPreviewPlaceholder.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
-
-export function PatternPreviewPlaceholder() {
- return (
- <Card className="p-0 gap-0 lg:h-full animate-fadeIn flex flex-col">
- <CardHeader className="p-6 pb-4 border-b-2 border-gray-300 dark:border-gray-600">
- <CardTitle className="text-base lg:text-lg">Pattern Preview</CardTitle>
- </CardHeader>
- <CardContent className="p-6 flex-1 flex flex-col">
- <div className="h-[400px] sm:h-[500px] lg:flex-1 flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-700 dark:to-gray-800 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600 relative overflow-hidden">
- {/* Decorative background pattern */}
- <div className="absolute inset-0 opacity-5 dark:opacity-10">
- <div className="absolute top-10 left-10 w-32 h-32 border-4 border-gray-400 dark:border-gray-500 rounded-full"></div>
- <div className="absolute bottom-10 right-10 w-40 h-40 border-4 border-gray-400 dark:border-gray-500 rounded-full"></div>
- <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-48 h-48 border-4 border-gray-400 dark:border-gray-500 rounded-full"></div>
- </div>
-
- <div className="text-center relative z-10">
- <div className="relative inline-block mb-6">
- <svg
- className="w-28 h-28 mx-auto text-gray-300 dark:text-gray-600"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={1.5}
- d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"
- />
- </svg>
- <div className="absolute -top-2 -right-2 w-8 h-8 bg-primary-100 dark:bg-primary-900 rounded-full flex items-center justify-center">
- <svg
- className="w-5 h-5 text-primary-600 dark:text-primary-400"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M12 4v16m8-8H4"
- />
- </svg>
- </div>
- </div>
- <h3 className="text-gray-700 dark:text-gray-200 text-base lg:text-lg font-semibold mb-2">
- No Pattern Loaded
- </h3>
- <p className="text-gray-500 dark:text-gray-400 text-sm mb-4 max-w-sm mx-auto">
- Connect to your machine and choose a PES embroidery file to see your
- design preview
- </p>
- <div className="flex items-center justify-center gap-6 text-xs text-gray-400 dark:text-gray-500">
- <div className="flex items-center gap-1">
- <div className="w-2 h-2 bg-primary-400 dark:bg-primary-500 rounded-full"></div>
- <span>Drag to Position</span>
- </div>
- <div className="flex items-center gap-1">
- <div className="w-2 h-2 bg-success-400 dark:bg-success-500 rounded-full"></div>
- <span>Zoom & Pan</span>
- </div>
- <div className="flex items-center gap-1">
- <div className="w-2 h-2 bg-accent-400 dark:bg-accent-500 rounded-full"></div>
- <span>Real-time Preview</span>
- </div>
- </div>
- </div>
- </div>
- </CardContent>
- </Card>
- );
-}
Served by rngit 1.3.3 - Generated in 0.07s